package com.coolxiaoyao.web.http;


import com.coolxiaoyao.common.http.ExceptionHandler;
import com.coolxiaoyao.web.util.SerializationUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ipfilter.IpFilterRule;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;

import static com.coolxiaoyao.web.config.SupportConfig.CHUNK_SIZE;
import static com.coolxiaoyao.web.config.SupportConfig.SUPPORT_HTTP_VERSION;

/**
 * @author Kerry on 18/09/19
 */

public class HttpServerHandler extends ChannelInboundHandlerAdapter {

    private static Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);


    private final DispatcherServlet dispatcherServlet;
    private final IpFilterRule filterRule;

    public HttpServerHandler(DispatcherServlet dispatcherServlet) {
        this.dispatcherServlet = dispatcherServlet;
        this.filterRule = null;
    }

    public HttpServerHandler(DispatcherServlet dispatcherServlet, IpFilterRule filterRule) {
        this.dispatcherServlet = dispatcherServlet;
        this.filterRule = filterRule;
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        if (filterRule != null) {
            if (!filterRule.matches((InetSocketAddress) ctx.channel().remoteAddress())) {
                ctx.close();
            }
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest request = (FullHttpRequest) msg;
        if (!request.decoderResult().isSuccess()) {
            ReferenceCountUtil.refCnt(msg);
            return;
        }

        Object responseData;
        try {
            responseData = dispatcherServlet.dispatcher(request);
        } catch (Throwable throwable) {
            DefaultHttpResponse response;
            ExceptionHandler exceptionHandler = dispatcherServlet.getApplicationContext().getExceptionHandler();
            if (exceptionHandler != null) {
                response = (DefaultFullHttpResponse) exceptionHandler.onThrowable(throwable);
            } else {
                response = new DefaultFullHttpResponse(SUPPORT_HTTP_VERSION, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
            ReferenceCountUtil.refCnt(msg);
            return;
        }

        if (responseData == null) {
            DefaultHttpResponse response = new DefaultFullHttpResponse(SUPPORT_HTTP_VERSION, HttpResponseStatus.NO_CONTENT);
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
            ReferenceCountUtil.refCnt(msg);
            return;
        }

        HttpObject httpObject;
        try {


            if (responseData instanceof HttpEntity) {
                HttpEntity entity = (HttpEntity) responseData;
                Object body = entity.getBody();
                if (body instanceof File) {
                    DefaultHttpResponse response = new DefaultHttpResponse(SUPPORT_HTTP_VERSION, HttpResponseStatus.OK);
                    // 文件下载
                    File file = (File) body;

                    MimetypesFileTypeMap m = new MimetypesFileTypeMap();
                    String contentType = m.getContentType(file.getPath());

                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
                    response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());

                    RandomAccessFile raf = new RandomAccessFile(file, "r");
                    long fileLength = raf.length();

                    HttpUtil.setContentLength(response, fileLength);

                    ctx.write(response);
                    if (ctx.pipeline().get(SslHandler.class) == null) {
                        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
                    } else {
                        ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, CHUNK_SIZE)), ctx.newProgressivePromise());
                    }
                    httpObject = LastHttpContent.EMPTY_LAST_CONTENT;
                } else {
                    DefaultFullHttpResponse response = this.handleHttp(responseData);
                    response.headers().add(entity.getHeaders());
                    httpObject = response;
                }
            } else {
                httpObject = this.handleHttp(responseData);
            }
        } catch (Exception e) {
            DefaultHttpResponse response;
            ExceptionHandler exceptionHandler = dispatcherServlet.getApplicationContext().getExceptionHandler();
            if (exceptionHandler != null) {
                response = (DefaultFullHttpResponse) exceptionHandler.onThrowable(e);
            } else {
                response = new DefaultFullHttpResponse(SUPPORT_HTTP_VERSION, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
            ReferenceCountUtil.refCnt(msg);
            return;
        }
        ChannelFuture channelFuture = ctx.writeAndFlush(httpObject);
        if (request.protocolVersion().isKeepAliveDefault()) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
        ReferenceCountUtil.refCnt(msg);
    }

    private DefaultFullHttpResponse handleHttp(Object responseData) throws JsonProcessingException {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(SUPPORT_HTTP_VERSION, HttpResponseStatus.OK);
        if (responseData instanceof String) {
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        } else {
            responseData = SerializationUtil.toJson(responseData);
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        }
        response.content().writeBytes(Unpooled.copiedBuffer(responseData.toString(), CharsetUtil.UTF_8));
        return response;
    }
}
